package no.ffi.tpm.crypto;

import java.io.Serializable;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
import javax.trustedcomputing.TrustedComputingException;
import javax.trustedcomputing.tpm.TPMContext;
import javax.trustedcomputing.tpm.keys.IdentityKey;
import javax.trustedcomputing.tpm.keys.KeyManager;
import javax.trustedcomputing.tpm.keys.LegacyKey;
import javax.trustedcomputing.tpm.keys.StorageRootKey;
import javax.trustedcomputing.tpm.structures.PCRInfo;
import javax.trustedcomputing.tpm.structures.Secret;
import javax.trustedcomputing.tpm.structures.ValidationData;
import javax.trustedcomputing.tpm.tools.Binder;
import javax.trustedcomputing.tpm.tools.Certifier;
import javax.trustedcomputing.tpm.tools.Signer;
import no.ffi.gismoidm.certstore.CertificateFileOperations;
import no.ffi.gismoidm.idp.Config;

import no.ffi.tpm.DecodeTPMCertInfo;
import no.ffi.tpm.LegacyKeyCertifyInfo;
import no.ffi.tpm.TPMiSRequest;
import no.ffi.tpm.aik.AIKUtils;




public class TPMCryptoOperations extends CryptoOperations {
    
    //The TPM session
    private TPMContext context;
    //The Identity key of the current user (certified and installed by an admin)
    private IdentityKey aik;
    //The legacy key to be used for transactions, certified by the aik
    private LegacyKey legacyKey;
    //The key manager for this TPM session
    private KeyManager keyMan;
    //The storage root key of the TPM must always be loaded
    private StorageRootKey srk;
    //The name of PEM file containting the CA certificate
    private String trustAnchorPemFilename;
    //The AIK certificate returned by the IdP, just a serial number in our case
    private String aikCert;
    //The user certificate
    private X509Certificate userCert, myTrustAnchorCert;;
    //The UUID of the Legacy key, used to reload the key if needed
    private String legacyUUID;
    //The password of the legacy key, stored in case an automatic reload is needed
    private String legacyPass;
    private String myDistinguishedName;
    
    //Initialize the class without a user, not really useful but to maintain compatibility
    //with the CeryptoOperationFactory interface
    TPMCryptoOperations(){
        
        initialize("");
    }
    
    /**
     * Initialize the CryptoOperations object with the specific user data taken
     * from a property file
     * @param user The user
     */
     TPMCryptoOperations(String appPrefix){
          if (!appPrefix.endsWith("_")) 
            appPrefix = appPrefix + "_";
        initialize(appPrefix);
    }
    
    /**
     * Read the property file of the user and initialize the object by loading the 
     * AIK preinstalled and certified by an admin and the user keys from a keystore.
     * The keystore password should be given as a parameter, rather than be given
     * in the property file.
     * @param user
     * @return a boolean stating that the initialization was successful.
     */ 
    public boolean initialize(String appPrefix) {
        System.setProperty("jsr321.tpmcontextimpl", "iaik.tc.jsr321.tpm.TPMContextImpl");
        //Load the property file of the user
        Config.loadPropertiesFrom(null);
        //Config.loadPropertiesFrom(null);
        boolean init=false;
        //Load the AIK information. An AIK should always exists at this point
        String aikUUID=Config.getProperty(appPrefix + "AIK_UUID");
        String aikPass=Config.getProperty(appPrefix + "AIK_PASS");
        this.aikCert=Config.getProperty(appPrefix + "AIK_SNR");
        //Load the legacy key information, which might be empty
        //legacyUUID = TPMConfig.getProperty("LEGACY-KEY-UUID");//UUID for TPM
        //legacyPass= TPMConfig.getProperty("LEGACY-KEY-PASS");
        //Load CA information
        //this.trustAnchorPemFilename = Config.getProperty("CA-CERT");
        //Load user certificate information
        String userCertPemFile=Config.getProperty(appPrefix + "USER_CERT");
        
       try {
            //Initialize the object fields by loading keys and certificates
            userCert=CertificateFileOperations.loadPemCert(userCertPemFile);
            this.myDistinguishedName=userCert.getSubjectX500Principal().toString();//CertificateFileOperations.getX509CertSubjectHash(userCert);
            this.myPubKey=userCert.getPublicKey();
            String userKeyStoreFile=Config.getProperty(appPrefix + "KEYSTORE_FILENAME");
            String userKeyStoreFilePass=Config.getProperty(appPrefix + "KEYSTORE_PASSWORD");
            System.out.println("xxxxKEYSTORE_FILENAME="+userKeyStoreFile+"xxKEYSTORE_PASSWORD"+userKeyStoreFilePass);
            this.myPrivKey=CertificateFileOperations.loadPkcsKey(userKeyStoreFile, userKeyStoreFilePass, userKeyStoreFilePass);
            this.trustAnchorPemFilename=Config.getProperty(appPrefix + "TRUSTANCHOR_FILENAME");//"idp.ffi.no.pem";
            this.myTrustAnchorCert=CertificateFileOperations.loadPemCert(trustAnchorPemFilename);
            //Initialize TPM and load SRK and AIK. The legacy key is loaded only
            //when an IS is to be requested
            System.out.println("IN TPMCypto "+System.getProperty("jsr321.tpmcontextimpl"));
            this.context=TPMContext.getInstance();
            context.connect(null);
            this.keyMan=this.context.getKeyManager();
            this.srk=this.keyMan.loadStorageRootKey(Secret.WELL_KNOWN_SECRET);
            this.aik=(IdentityKey) this.keyMan.loadTPMSystemKey(srk, UUID.fromString(aikUUID), this.context.getSecret(aikPass.toCharArray()));
            PublicKey aik=this.aik.getPublicKey();
            /*if(!legacyUUID.equals("")){
                this.legacyKey= (LegacyKey) this.keyMan.loadTPMSystemKey(srk, UUID.fromString(legacyUUID),this.context.getSecret(legacyPass.toCharArray()));
            }*/
            
            init=true;
        } catch (Exception ex) {
            Logger.getLogger(TPMCryptoOperations.class.getName()).log(Level.SEVERE, null, ex);
        } 
       return init;
    }



    /**
     * 
     * @param eo
     * @return
     * @throws Exception 
     */
    @Override
    public byte[] decrypt(EncryptedObject eo) throws Exception {
       Binder binder=this.context.getBinder();
       byte[] aesKeyEncoded=binder.unbind(eo.encryptedKey, legacyKey);
       SecretKey sessKey = new SecretKeySpec(aesKeyEncoded,AES);
       eo.secretKey = sessKey;
       /*Cipher cip = Cipher.getInstance(symmetricAlg);
       cip.init(Cipher.DECRYPT_MODE,sessKey);
       byte[] plaintext = cip.doFinal(eo.encryptedBuffer);
       return deserialize(plaintext);*/
       return decrypt(eo.encryptedBuffer,sessKey);
    }

    @Override
    public SignedObject sign(Serializable object) throws Exception {
        byte[] buffer = serialize(object);
        Signer signer=this.context.getSigner();
        byte[] signature = signer.sign(buffer, legacyKey);
        return new SignedObject(buffer,signature);
    }

   

    @Override
    public X509Certificate certifyPublicKey() throws Exception {
         return userCert;
    }

    /*
     * TPM specific operation to certify the current legacy key with an AIK.
     * Returns a specific data structure called ValidationData that contains the 
     */
   // @Override
    public Serializable getISrequest(String legacyKeysecret, byte[] nonce) {
        //If no legacy key exists, create one first and store the related information
        //in the user property file
        TPMiSRequest iSReq = null;
        Secret legacySecret = this.context.getSecret(legacyKeysecret.toCharArray());
        //if (this.legacyUUID.equals("")) {
            
            try {
                int[] pcrIdxs={0,1,2,3,4,5,6,7,8,9,10,11,12};
                PCRInfo pcrValues=context.getTPMInstance().readPCR(pcrIdxs);
               
                this.legacyKey = this.keyMan.createLegacyKey(srk, legacySecret, null, false, true, true, 2048, pcrValues);
                //legacyUUID = this.legacyKey.getUUID().toString();
                this.legacyPass = legacyKeysecret;
                //this.keyMan.storeTPMSystemKey(srk, this.legacyKey, UUID.fromString(legacyUUID));
                //TPMConfig.setProperty("LEGACY-KEY-UUID", legacyUUID);
                //TPMConfig.setProperty("LEGACY-KEY-PASS", legacyKeysecret);
            } catch (TrustedComputingException ex) {
                Logger.getLogger(TPMCryptoOperations.class.getName()).log(Level.SEVERE, null, ex);
            }
        /*} else {
            try {
                this.legacyKey=(LegacyKey) this.keyMan.loadTPMSystemKey(srk,UUID.fromString(legacyUUID) , legacySecret);
            } catch (TrustedComputingException ex) {
                Logger.getLogger(TPMCryptoOperations.class.getName()).log(Level.SEVERE, null, ex);
            }
        }*/
       
            
            ValidationData val = null;
            try {
                Certifier cert = this.context.getCertifier();
                val = cert.certifyKey(this.legacyKey, aik, context.getDigest(nonce));
                DecodeTPMCertInfo tpmInfo = new DecodeTPMCertInfo(val.getData());
                
                LegacyKeyCertifyInfo certificateRequest = new LegacyKeyCertifyInfo(val.getData(), getPubKey(), this.getAIK(), val.getValidationData(), this.aikCert, tpmInfo.getKeyDigIndex(), tpmInfo.getNonceIndex());
                certificateRequest.setSubjId(this.myDistinguishedName);
                Signature s = Signature.getInstance(signatureAlgorithm);
                s.initSign(myPrivKey);
                //s.update(getMyDistinguishedName().getBytes());
                s.update(serialize(certificateRequest));
                byte[] signature = s.sign();
                iSReq = new TPMiSRequest(certificateRequest, signature,tpmInfo.getPcrInfo());
            } catch (SignatureException ex) {
                Logger.getLogger(TPMCryptoOperations.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InvalidKeyException ex) {
                Logger.getLogger(TPMCryptoOperations.class.getName()).log(Level.SEVERE, null, ex);
            } catch (NoSuchAlgorithmException ex) {
                Logger.getLogger(TPMCryptoOperations.class.getName()).log(Level.SEVERE, null, ex);
            } catch (TrustedComputingException | UnsupportedOperationException ex) {
                Logger.getLogger(TPMCryptoOperations.class.getName()).log(Level.SEVERE, null, ex);
            }

        
        return iSReq;
    }

    @Override
    public PublicKey getPubKey() {
        PublicKey key=null;
        
        try {
             key=this.legacyKey.getPublicKey();
        } catch (TrustedComputingException ex) {
            Logger.getLogger(TPMCryptoOperations.class.getName()).log(Level.SEVERE, null, ex);
        }
        return key;
    }
    
     public PublicKey getUserPubKey() {
         return this.myPubKey;
     }
    
       public PrivateKey getUserPrvKey() {
         return this.myPrivKey;
     }
       
    public PublicKey getAIK() {
        PublicKey key=null;
        
        try {
             key=this.aik.getPublicKey();
        } catch (TrustedComputingException ex) {
            Logger.getLogger(TPMCryptoOperations.class.getName()).log(Level.SEVERE, null, ex);
        }
        return key;
    }

   

    @Override
    public X509Certificate getTrustAnchorCert() {
       return this.myTrustAnchorCert;
    }

    @Override
    public PublicKey getTrustAnchor() {
        return this.myTrustAnchorCert.getPublicKey();
    }

    @Override
    public X500Principal getSubjectPrincipal() {
        return userCert.getSubjectX500Principal();
    }

    @Override
    public Serializable getISrequest() {
        return getISrequest("legacyPass",new byte[20]);
    }
    
    @Override
    public String getMyDistinguishedName() {
        return (String)getISrequest();
    }

    
}
